package com.umfb.config.zk;


import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.data.Stat;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;


/**
 * Created by zcah on 18-4-19.
 */
public class ZooKeeperConfig implements UmfbConfig {
    protected final Log logger = LogFactory.getLog(getClass());
    private CuratorFramework _client;
    private final String server;
    private final String ns;
    private final String proj;

    public ZooKeeperConfig(String server, String ns, String proj) {
        this.server = server;
        this.ns = ns;
        this.proj = proj;
    }

    private CuratorFramework getClient() {
        if (this._client == null) {
            this._client = ZooKeeperFactory.get(server, ns);
        }
        return this._client;
    }


    public byte[] getConfig(String path) throws Exception {
        CuratorFramework client = this.getClient();
        if (!exists(client, path)) {
            throw new RuntimeException("Path " + path + " does not exists.");
        }
        return client.getData().forPath(path);
    }

    /**
     * 解析系统需要使用proj分隔配置的命名空间 例如 /prefix.split-proj-ns=proj,db,redis,mongo
     * 则根目录下的此４个路径使用命名空间解析 /proj/admin/xx.. /db/admin/xx.. /redis/admin/xx..
     * /mongo/admin/xx.. admin项目读取的配置 /proj/user/xx.. /db/user/xx..
     * /redis/user/xx.. /mongo/user/xx.. user项目读取的配置 配置加载顺序如
     * /prefix.split-proj-ns 指定的顺序
     *
     * @return 需进行解析的ｎｓ空间
     * @throws Exception
     */
    private Set<String> getPrefixProjNS() throws Exception {
        CuratorFramework client = this.getClient();
        // 解析需要分隔proj的根路径,此功能可用于排序
        // 按照prefix.split-proj-ns的顺序读取
        Set<String> projs = new LinkedHashSet<String>() {
            /**
             * @Fields serialVersionUID :(用一句话描述这个变量表示什么)
             */
            private static final long serialVersionUID = 1L;

            {
                add("proj");
            }
        };

        if (this.proj != null) {
            if (exists(client, "/prefix.split-proj-ns")) {
                String valve = new String(client.getData().forPath("/prefix.split-proj-ns")).trim();
                // 此处的处理方式可支持排序
                LinkedHashSet<String> newProjs = new LinkedHashSet<String>(
                        Splitter.on(",").omitEmptyStrings().splitToList(valve));
                newProjs.addAll(projs);
                projs = newProjs;
            }
        }
        return projs;
    }

    public byte[] getAllConfigForOnce() throws Exception {
        HashMap<String, String> keyvalue = new HashMap<String, String>();
        String separator = System.getProperty("line.separator");

        CuratorFramework client = this.getClient();

        Set<String> projs = getPrefixProjNS();

        getNodeConfig(keyvalue, "/", projs);

        if (this.proj != null) {
            for (String nsPrefix : projs) {
                String projRootPath = "/" + nsPrefix + "/" + this.proj;

                if (exists(client, projRootPath)) {
                    getNodeConfig(keyvalue, projRootPath, null);
                }
            }
        }

        client.close();
        _client = null;

        HashMap<String, String> kvRefresh = refreshKeyValueMapper(keyvalue);
        Joiner.MapJoiner joiner = Joiner.on(separator).withKeyValueSeparator("=");

        String kvStr = joiner.join(kvRefresh);
        logger.info("WSC Read ZK Config [" + kvStr + "]");
        return kvStr.getBytes("UTF-8");
    }

    private HashMap<String, String> refreshKeyValueMapper(HashMap<String, String> kvMapper) {
        HashMap<String, String> newMapper = new HashMap<String, String>();
        for (String key : kvMapper.keySet()) {
            String value = resolveValue(kvMapper, kvMapper.get(key));

            newMapper.put(key, value);
        }
        return newMapper;
    }

    private void getNodeConfig(HashMap<String, String> keyvalue, String path, Set<String> excludeNode)
            throws Exception {
        // 解析配置项目，目前仅仅支持两级路径解析，应该够用
        CuratorFramework client = this.getClient();
        List<String> children = client.getChildren().forPath(path);

        String forJoinPath = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;

        for (String node : children) {
            if (excludeNode != null && ("prefix.split-proj-ns".equals(node) || excludeNode.contains(node)))
                continue;

            // 解析第一级路径
            String currentNodePath = forJoinPath + "/" + node;
            String value;
            try {
                value = new String(client.getData().forPath(currentNodePath)).trim();
            } catch (IllegalArgumentException ex) {
                continue;
            }
            if (value.equals("-") || value.isEmpty()) {
                // 解析第二级路径
                List<String> threeChildren = client.getChildren().forPath(currentNodePath);
                for (String threeNode : threeChildren) {
                    String threeValue;
                    try {
                        threeValue = new String(
                                client.getData().forPath(Joiner.on('/').skipNulls().join(forJoinPath, node, threeNode)))
                                .trim();
                    } catch (IllegalArgumentException ex) {
                        continue;
                    }
                    keyvalue.put(Joiner.on('.').join(node, threeNode), threeValue);
                }
            } else {
                keyvalue.put(node, value);
            }
        }
    }

    /**
     * 解析value 的 ref 格式 例如 ref:system.server.mls|http://127.0.0.1:8030
     * 会查找系统第一次解析时是否存在 system.server.mls 如果存在，则使用之前设置的值，否则使用默认值
     *
     * @param keyvalue
     *            首次解析的ｋｖ字典
     * @param value
     *            需要二次解析的 value
     * @return 二次解析后的value
     */

    private String resolveValue(HashMap<String, String> keyvalue, String value) {
        if ("null".equals(value)) {
            return "";
        }

        if (value.startsWith("ref:")) {
            String refKeyAndDefaultValue = value.substring(4);
            Object[] split = Splitter.on("|").omitEmptyStrings().splitToList(refKeyAndDefaultValue).toArray();
            String refKey = (String) split[0];
            if (keyvalue.containsKey(refKey)) {
                return keyvalue.get(refKey);
            } else {
                if (split.length == 2) {
                    return (String) split[1];
                }
            }
        }
        return value;
    }

    private boolean exists(CuratorFramework client, String path) throws Exception {
        Stat stat = client.checkExists().forPath(path);
        return !(stat == null);
    }

}
